const CONNECT_URL = (Site.DEBUG) ? 'http://localhost:8081/ptz/connectToPTZ' : 'http://www.projectlive3d.com/ptz/connectToPTZ';
const GET_CONTROL_URL = (Site.DEBUG) ? 'http://localhost:8081/ptz/getPTZControl' : 'http://www.projectlive3d.com/ptz/getPTZControl';
const MOVE_CAMERA_URL = (Site.DEBUG) ? 'http://localhost:8081/ptz/movePTZCamera' : 'http://www.projectlive3d.com/ptz/movePTZCamera';

function ControlCamera(url, baseURL, ptzCalibration){
	// url is image url, baseURL is, e.g., http://grizcam.ito.umt.edu/
	// update the url.
	this.url = url;
	this.baseURL = baseURL;
	if (document.getElementById('smallimage')){
		this.img = document.getElementById('smallimage');
	} else{
		this.img = null;
	}
	this.ptzCalib = ptzCalibration;
	
	// build in the support to connect and maintain a connect to the camera.
	this.message = document.getElementById('cameraMessage');
	
	// add the 3D point.
	this.focusPoint = new Point3D(ptzCalibration.origin.lat, ptzCalibration.origin.lon, ptzCalibration.origin.alt, 'orange');
	this.dragging = false;
	this.focusPoint.draw();
	this.cameraLockedOn = false;
	
	var me = this;
	google.earth.addEventListener(this.focusPoint.placemark, "mousedown", function(event){me.geMouseDownHandler(event)});
	google.earth.addEventListener(this.focusPoint.placemark, "mouseup", function(event){me.geMouseUpHandler(event)});
	
	this.connectionId = null;
	this.state = 'disconnected'
}

ControlCamera.prototype.moveCameraLoop = function(){
    // the zoom level is proportional to how far away the point is.
    var cameraCenter = this.ptzCalib.cameraPosition;
    var d = distance(cameraCenter.lat,cameraCenter.lon,cameraCenter.alt,this.focusPoint.lat,this.focusPoint.lon,this.focusPoint.alt);
    
    // zoom in maximally when the distance is greater than 1000 m.
    // zoom in minimally when the distance is less than 10 m.
    d = Math.min(d, 750);
    d = Math.max(d, 10);
    var zoom = (d - 10)/(750 - 10);
    
    this.optimizeForPanTilt(zoom);
}

ControlCamera.prototype.geMouseDownHandler = function(event){
	this.dragging = true;
	this.focusPoint.lastY = -1;
	var me = this;
	google.earth.addEventListener(this.focusPoint.placemark, "mousemove", function(event){
		if (me.dragging){
			me.focusPoint.movePoint(event);
		 	this.cameraLockedOn = false;
		}});
	
}

ControlCamera.prototype.geMouseUpHandler = function(event){	
	this.dragging = false;
	this.focusPoint.lastY = -1;
	var me = this;
	google.earth.removeEventListener(this.focusPoint.placemark, "mousemove", function(event){
		if (me.dragging){
			me.focusPoint.movePoint(event);
		 	this.cameraLockedOn = false;
		}});}

// refreshes the HTML image.
ControlCamera.prototype.refreshImage = function(){
	var tmp = new Date();
	tmp = "?"+tmp.getTime() 
	this.img.setAttribute('src', this.url + tmp);
}

// the goal is to find the pan/tilt combination that places the projection of
// the focus point in the center of the image.  This function will search for
// the pan/tilt value (in integral values, e.g., [-17000,17000]) that minimizes the projection distance.
// input: zoom, a float between 0.0 and 1.0, the current zoom level.
ControlCamera.prototype.optimizeForPanTilt = function(zoom){
	if (this.cameraLockedOn){
		// no need to move the camera.
		return;
	}
	
	
	var zoomIntegral = Math.floor(zoom * (this.ptzCalib.zoomRanges[1] - this.ptzCalib.zoomRanges[0]) + this.ptzCalib.zoomRanges[0]);
	var bestPan = null;
	var bestTilt = null;
	var bestValue = null;
	const numPans = 50;
	const numTilts = 25;
	var panDelta = (this.ptzCalib.panRanges[1] - this.ptzCalib.panRanges[0]) / numPans;
	var tiltDelta = (this.ptzCalib.tiltRanges[1] - this.ptzCalib.tiltRanges[0]) / numTilts;

	var optimalU = this.ptzCalib.width / 2;
	var optimalV = this.ptzCalib.height / 2;
	
	// convert the focus point from lat/lon/alt to x/y/z.
	var origin = this.ptzCalib.origin;
	var metersPerLat = distance(origin.lat, origin.lon, 0, origin.lat + 0.00001, origin.lon, 0) / 0.00001;
	var metersPerLon = distance(origin.lat, origin.lon, 0, origin.lat, origin.lon + 0.00001, 0) / 0.00001;
	
	var x = (this.focusPoint.lon - origin.lon) * metersPerLon;
	var y = (this.focusPoint.lat - origin.lat) * metersPerLat;
	var z = this.focusPoint.alt - origin.alt;
	for (var i = 0; i < numPans; i++){
		var pan = Math.floor(this.ptzCalib.panRanges[0] + panDelta * i);
		for (var j = 0; j < numTilts; j++){
			var tilt = Math.floor(this.ptzCalib.tiltRanges[0] + tiltDelta * j);
			
			// update the pan/tilt/coordinates.
			this.ptzCalib.currentPan = pan;
			this.ptzCalib.currentTilt = tilt;
			this.ptzCalib.currentZoom = zoomIntegral;
			this.ptzCalib.updateM();
			
			// see how good of an estimate this is.
			var M = this.ptzCalib.M.values;
			
			var w  = M[2][0] * x + M[2][1] * y + M[2][2] * z + M[2][3];
			if (w < 0){
				var uw = M[0][0] * x + M[0][1] * y + M[0][2] * z + M[0][3];
				var vw = M[1][0] * x + M[1][1] * y + M[1][2] * z + M[1][3];
				var u = uw / w;
				var v = vw / w;
				
				var d = l2distance(optimalU, optimalV, u,v);
				
				if (bestValue == null || d < bestValue){
					bestValue = d;
					bestPan = pan;
					bestTilt = tilt;
				}
			}
			
			
		}
		
	}
	this.moveCamera(bestPan, bestTilt, zoomIntegral)
}



// moves the camera to the pan/tilt/zoom configuration.  If the camera
// has not yet established a connection to the site, it will do that first.
// If the camera still needs to enter a queue, it will do that next.
// Then, if nobody else is in the queue, the camera moves to the
// designated PTZ location.
ControlCamera.prototype.moveCamera = function(pan, tilt, zoom){
	// first, try to establish a connection.

    var url = "nostate";

	if (this.state == 'disconnected'){
		// connect.
		var http = createRequest();
		url = CONNECT_URL + '?baseURL=' + this.baseURL;
		var me = this;
		http.open("GET", url, true);
		http.onreadystatechange = function(){
			if(http.readyState == 4){
				var response = http.responseText;
				if (response != '-1'){
					me.connectionID = response;
					me.state = 'connected'
					me.message.innerHTML = 'Connected:' + response;
				}
			}
		};
		this.message.innerHTMl = 'Connecting...'
		this.state = 'connecting'
		http.send(null);
		this.cameraLockedOn = false;
	}

	else if (this.state == 'connected'){
		// then, try to get control of the camera. 
		//http://grizcam.ito.umt.edu/-wvhttp-01-/GetCameraControl?connection_id=PUTIDHERE
		var http = createRequest();
		url = GET_CONTROL_URL + '?baseURL=' + this.baseURL + '&connectionID=' + this.connectionID
		var me = this;
		http.open("GET", url, true);
		http.onreadystatechange = function(){
			if(http.readyState == 4){
				var response = http.responseText;
				// response should be "OK."
				if (response == '1'){
					me.state = 'inqueue';
					me.message.innerHTML = 'In queue for camera control.'
				} else{
					me.state = 'disconnected';
					this.connectionID = null;
					me.message.innerHTML = 'Disconnected.'
				}
			}
		};
		this.message.innerHTMl = 'Going into camera queue...'
		this.state = 'requestingControl'
		http.send(null);
		this.cameraLockedOn = false;
	}
			
	else if (this.state == 'inqueue'){
		// try to gain control of the camera.
		//http://grizcam.ito.umt.edu/-wvhttp-01-/OperateCamera?pan=5000&connection_id=4009-b461
		var http = createRequest();
		url = MOVE_CAMERA_URL + '?baseURL=' + this.baseURL + '&connectionID=' + this.connectionID + '&pan=' + pan + '&tilt=' + tilt + '&zoom=' + zoom
		var me = this;
		http.open("GET", url, true);
		http.onreadystatechange = function(){
			if(http.readyState == 4){
				var response = http.responseText;
				// response should be "OK."
				if (response == 'success'){
					me.message.innerHTML = 'The camera is in our control.'
					this.cameraLockedOn = true;
				 	
				} else if (response == 'not your turn'){
					me.message.innerHTML = 'Someone else is using the camera.'
				} else if (response == 'unknown connection'){
					me.state = 'disconnected'
					this.connectionID = null;
					me.message.innerHTML = 'Disconnected.'
				} else {
					me.state = 'disconnected'
					this.connectionID = null;
					me.message.innerHTML = 'Some unknown error occurred.'
				}
			}
		};
		http.send(null);
	}else {
	    this.state = 'disconnected';
	}
}